package health_system.health_system.service.impl;

import health_system.health_system.dto.*;
import health_system.health_system.entity.Moment;
import health_system.health_system.entity.MomentMedia;
import health_system.health_system.entity.User;
import health_system.health_system.exception.BusinessException;
import health_system.health_system.exception.MomentMediaNotFoundException;
import health_system.health_system.exception.MomentNotFoundException;
import health_system.health_system.exception.ResourceNotFoundException;
import health_system.health_system.repository.MomentMediaRepository;
import health_system.health_system.repository.MomentRepository;
import health_system.health_system.repository.UserRepository;
import health_system.health_system.service.FileStorageService;
import health_system.health_system.service.MomentMediaService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;


import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class MomentMediaServiceImpl implements MomentMediaService {

    private final MomentMediaRepository mediaRepository;
    private final MomentRepository momentRepository;
    private final UserRepository userRepository;
    private final FileStorageService fileStorageService;

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentMedia", "userMedia", "mediaCount"}, allEntries = true)
    })
    public MomentMediaDTO createMedia(MomentMediaCreateDTO createDTO) {
        log.debug("Creating media for moment ID: {}", createDTO.getMomentId());
        
        Moment moment = momentRepository.findById(createDTO.getMomentId())
                .orElseThrow(() -> new MomentNotFoundException(createDTO.getMomentId()));
        
        MomentMedia media = new MomentMedia();
        media.setMoment(moment);
        media.setMediaUrl(createDTO.getMediaUrl());
        media.setMediaType(createDTO.getMediaType());
        media.setOrderIndex(createDTO.getOrderIndex());
        media.setThumbnailUrl(createDTO.getThumbnailUrl());
        media.setDuration(createDTO.getDuration());
        
        try {
            MomentMedia savedMedia = mediaRepository.save(media);
            log.info("Successfully created media with ID: {} for moment ID: {}", 
                    savedMedia.getId(), moment.getId());
            return MomentMediaDTO.fromEntity(savedMedia);
        } catch (DataIntegrityViolationException e) {
            log.error("Failed to create media due to data integrity violation", e);
            throw new BusinessException("Failed to create media. Check your input data.", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentMedia", "userMedia", "mediaCount"}, allEntries = true)
    })
    public MomentMediaDTO uploadMedia(MultipartFile file, Long momentId, MomentMedia.MediaType mediaType, Integer orderIndex) {
        log.debug("Uploading media file for moment ID: {}", momentId);
        
        if (file.isEmpty()) {
            throw new BusinessException("Cannot upload empty file");
        }
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));

        String fileExtension = getFileExtension(file.getOriginalFilename());
        String mediaUrl = fileStorageService.store(file, "moments/" + momentId);

        MomentMediaCreateDTO createDTO = new MomentMediaCreateDTO();
        createDTO.setMomentId(momentId);
        createDTO.setMediaUrl(mediaUrl);
        createDTO.setMediaType(mediaType);
        createDTO.setOrderIndex(orderIndex != null ? orderIndex : 0);

        // Generate thumbnail for videos
        if (mediaType == MomentMedia.MediaType.VIDEO) {
            String thumbnailUrl = generateThumbnail(mediaUrl);
            createDTO.setThumbnailUrl(thumbnailUrl);
        }

        return createMedia(createDTO);
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentMedia", "userMedia", "mediaCount"}, allEntries = true)
    })
    public List<MomentMediaDTO> batchUploadMedia(List<MultipartFile> files, Long momentId) {
        log.debug("Batch uploading {} media files for moment ID: {}", files.size(), momentId);
        
        if (files.isEmpty()) {
            return Collections.emptyList();
        }
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        List<MomentMediaDTO> results = new ArrayList<>();
        
        int orderIndex = 0;
        for (MultipartFile file : files) {
            try {
                String contentType = file.getContentType();
                MomentMedia.MediaType mediaType = determineMediaType(contentType);
                
                MomentMediaDTO mediaDTO = uploadMedia(file, momentId, mediaType, orderIndex++);
                results.add(mediaDTO);
            } catch (Exception e) {
                log.error("Failed to upload file: {} for moment ID: {}", file.getOriginalFilename(), momentId, e);
                // Continue with next file instead of failing the whole batch
            }
        }
        
        return results;
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentMedia", "userMedia", "mediaCount"}, allEntries = true)
    })
    public MomentMediaDTO updateMedia(Long id, MomentMediaUpdateDTO updateDTO) {
        log.debug("Updating media with ID: {}", id);
        
        MomentMedia media = getMediaEntityById(id);
        
        if (updateDTO.getMediaUrl() != null) {
            media.setMediaUrl(updateDTO.getMediaUrl());
        }
        
        if (updateDTO.getMediaType() != null) {
            media.setMediaType(updateDTO.getMediaType());
        }
        
        if (updateDTO.getOrderIndex() != null) {
            media.setOrderIndex(updateDTO.getOrderIndex());
        }
        
        if (updateDTO.getThumbnailUrl() != null) {
            media.setThumbnailUrl(updateDTO.getThumbnailUrl());
        }
        
        if (updateDTO.getDuration() != null) {
            media.setDuration(updateDTO.getDuration());
        }
        
        try {
            MomentMedia updatedMedia = mediaRepository.save(media);
            log.info("Successfully updated media with ID: {}", updatedMedia.getId());
            return MomentMediaDTO.fromEntity(updatedMedia);
        } catch (DataIntegrityViolationException e) {
            log.error("Failed to update media with ID: {} due to data integrity violation", id, e);
            throw new BusinessException("Failed to update media. Check your input data.", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentMedia", "userMedia", "mediaCount"}, allEntries = true)
    })
    public void deleteMedia(Long id) {
        log.debug("Deleting media with ID: {}", id);
        
        MomentMedia media = getMediaEntityById(id);
        
        try {
            // Attempt to delete the file from storage first
            if (media.getMediaUrl() != null) {
                fileStorageService.deleteFile(media.getMediaUrl());
            }
            
            if (media.getThumbnailUrl() != null) {
                fileStorageService.deleteFile(media.getThumbnailUrl());
            }
            
            mediaRepository.deleteById(id);
            log.info("Successfully deleted media with ID: {}", id);
        } catch (Exception e) {
            log.error("Failed to delete media with ID: {}", id, e);
            throw new BusinessException("Failed to delete media with ID: " + id, e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentMedia", key = "#id")
    public MomentMediaDTO getMediaById(Long id) {
        log.debug("Getting media by ID: {}", id);
        
        MomentMedia media = getMediaEntityById(id);
        return MomentMediaDTO.fromEntity(media);
    }

    @Override
    @Transactional(readOnly = true)
    public MomentMedia getMediaEntityById(Long id) {
        log.debug("Getting media entity by ID: {}", id);
        
        return mediaRepository.findById(id)
                .orElseThrow(() -> new MomentMediaNotFoundException(id));
    }

    @Override
    @Transactional(readOnly = true)
    public Optional<MomentMedia> findMediaEntityById(Long id) {
        log.debug("Finding media entity by ID: {}", id);
        
        return mediaRepository.findById(id);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentMediaList", key = "#momentId")
    public List<MomentMediaDTO> getMediaByMomentId(Long momentId) {
        log.debug("Getting all media for moment ID: {}", momentId);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        List<MomentMedia> mediaList = mediaRepository.findByMomentOrderByOrderIndex(moment);
        
        if (mediaList.isEmpty()) {
            return Collections.emptyList();
        }
        
        return mediaList.stream()
                .map(MomentMediaDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userMedia", key = "#userId")
    public List<MomentMediaDTO> getMediaByUserId(Long userId) {
        log.debug("Getting all media for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<MomentMedia> mediaList = mediaRepository.findByUserOrderByCreatedAtDesc(user);
        
        if (mediaList.isEmpty()) {
            return Collections.emptyList();
        }
        
        return mediaList.stream()
                .map(MomentMediaDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userMediaPaged", 
              key = "#userId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentMediaDTO> getMediaByUserId(Long userId, Pageable pageable) {
        log.debug("Getting paginated media for user ID: {} with page: {}", 
                userId, pageable.getPageNumber());
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Page<MomentMedia> mediaPage = mediaRepository.findByUserOrderByCreatedAtDesc(user, pageable);
        
        Page<MomentMediaDTO> dtoPage = mediaPage.map(MomentMediaDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentMediaByType", key = "#momentId + '-' + #mediaType")
    public List<MomentMediaDTO> getMediaByMomentIdAndType(Long momentId, MomentMedia.MediaType mediaType) {
        log.debug("Getting media for moment ID: {} with type: {}", momentId, mediaType);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        List<MomentMedia> mediaList = mediaRepository.findByMomentAndMediaType(moment, mediaType);
        
        if (mediaList.isEmpty()) {
            return Collections.emptyList();
        }
        
        return mediaList.stream()
                .map(MomentMediaDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "mediaCount", key = "#momentId + '-' + #mediaType")
    public Long countMediaByMomentIdAndType(Long momentId, MomentMedia.MediaType mediaType) {
        log.debug("Counting media for moment ID: {} with type: {}", momentId, mediaType);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        return mediaRepository.countByMomentAndMediaType(moment, mediaType);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "imagesVisibleToUser", 
              key = "#viewerId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public List<MomentMediaDTO> getImagesVisibleToUser(Long viewerId, Pageable pageable) {
        log.debug("Getting images visible to user ID: {} with page: {}", 
                viewerId, pageable.getPageNumber());
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        List<MomentMedia> mediaList = mediaRepository.findImagesVisibleToUser(viewer, pageable);
        
        if (mediaList.isEmpty()) {
            return Collections.emptyList();
        }
        
        return mediaList.stream()
                .map(MomentMediaDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "videosVisibleToUser", 
              key = "#viewerId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public List<MomentMediaDTO> getVideosVisibleToUser(Long viewerId, Pageable pageable) {
        log.debug("Getting videos visible to user ID: {} with page: {}", 
                viewerId, pageable.getPageNumber());
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        List<MomentMedia> mediaList = mediaRepository.findVideosVisibleToUser(viewer, pageable);
        
        if (mediaList.isEmpty()) {
            return Collections.emptyList();
        }
        
        return mediaList.stream()
                .map(MomentMediaDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "mediaCount", key = "#momentId")
    public Long countMediaByMomentId(Long momentId) {
        log.debug("Counting media for moment ID: {}", momentId);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        return mediaRepository.countByMoment(moment);
    }
    
    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentMedia", "userMedia", "mediaCount"}, allEntries = true)
    })
    public void deleteAllMediaForMoment(Long momentId) {
        log.debug("Deleting all media for moment ID: {}", momentId);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        // Get all media first to delete the files
        List<MomentMedia> mediaList = mediaRepository.findByMomentOrderByOrderIndex(moment);
        
        for (MomentMedia media : mediaList) {
            try {
                if (media.getMediaUrl() != null) {
                    fileStorageService.deleteFile(media.getMediaUrl());
                }
                
                if (media.getThumbnailUrl() != null) {
                    fileStorageService.deleteFile(media.getThumbnailUrl());
                }
            } catch (Exception e) {
                log.warn("Failed to delete file for media ID: {}", media.getId(), e);
                // Continue with next file
            }
        }
        
        try {
            mediaRepository.deleteByMoment(moment);
            log.info("Successfully deleted all media for moment ID: {}", momentId);
        } catch (Exception e) {
            log.error("Failed to delete all media for moment ID: {}", momentId, e);
            throw new BusinessException("Failed to delete all media for moment ID: " + momentId, e);
        }
    }
    
    @Override
    @Transactional(readOnly = true)
    public List<MomentMediaDTO> searchMediaByUserAndFilename(Long userId, String filename) {
        log.debug("Searching media for user ID: {} with filename: {}", userId, filename);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<MomentMedia> mediaList = mediaRepository.findByUserAndFilenameContaining(user, filename);
        
        if (mediaList.isEmpty()) {
            return Collections.emptyList();
        }
        
        return mediaList.stream()
                .map(MomentMediaDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentsWithMostMedia", 
              key = "#viewerId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public List<MomentWithMediaStatsDTO> getMomentsWithMostMediaVisibleToUser(Long viewerId, Pageable pageable) {
        log.debug("Getting moments with most media visible to user ID: {} with page: {}", 
                viewerId, pageable.getPageNumber());
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        List<Object[]> results = mediaRepository.findMomentsWithMostMediaVisibleToUser(viewer, pageable);
        
        if (results.isEmpty()) {
            return Collections.emptyList();
        }
        
        return results.stream()
                .map(result -> {
                    Moment moment = (Moment) result[0];
                    Long mediaCount = (Long) result[1];
                    
                    // Count by media type
                    Long imageCount = mediaRepository.countByMomentAndMediaType(moment, MomentMedia.MediaType.IMAGE);
                    Long videoCount = mediaRepository.countByMomentAndMediaType(moment, MomentMedia.MediaType.VIDEO);
                    
                    return MomentWithMediaStatsDTO.fromMomentAndStats(moment, mediaCount, imageCount, videoCount);
                })
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentMedia", "userMedia", "mediaCount"}, allEntries = true)
    })
    public void reorderMedia(Long momentId, List<Long> mediaIds) {
        log.debug("Reordering media for moment ID: {} with media IDs: {}", momentId, mediaIds);
        
        if (mediaIds == null || mediaIds.isEmpty()) {
            return;
        }
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        // Validate that all media belongs to this moment
        List<MomentMedia> mediaList = mediaRepository.findAllById(mediaIds);
        
        for (MomentMedia media : mediaList) {
            if (!media.getMoment().getId().equals(momentId)) {
                throw new BusinessException("Media ID: " + media.getId() + " does not belong to Moment ID: " + momentId);
            }
        }
        
        // Update order indexes
        for (int i = 0; i < mediaIds.size(); i++) {
            Long mediaId = mediaIds.get(i);
            Optional<MomentMedia> mediaOpt = mediaList.stream()
                    .filter(m -> m.getId().equals(mediaId))
                    .findFirst();
            
            if (mediaOpt.isPresent()) {
                MomentMedia media = mediaOpt.get();
                media.setOrderIndex(i);
                mediaRepository.save(media);
            }
        }
        
        log.info("Successfully reordered media for moment ID: {}", momentId);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "batchMediaCounts", key = "#momentIds.hashCode()")
    public Map<Long, Long> batchGetMediaCountsForMoments(List<Long> momentIds) {
        log.debug("Batch getting media counts for moments: {}", momentIds);
        
        if (momentIds == null || momentIds.isEmpty()) {
            return Collections.emptyMap();
        }
        
        Map<Long, Long> result = new HashMap<>();
        
        // Get all moments from the repository to avoid N+1 queries
        List<Moment> moments = momentRepository.findAllById(momentIds);
        
        // Count media for each moment
        for (Moment moment : moments) {
            Long count = mediaRepository.countByMoment(moment);
            result.put(moment.getId(), count);
        }
        
        return result;
    }
    
    @Override
    public String generateThumbnail(String videoUrl) {
        log.debug("Generating thumbnail for video URL: {}", videoUrl);
        
        // This would typically use a video processing library or service
        // For this implementation, we'll return a placeholder URL
        return videoUrl + ".thumbnail.jpg";
    }
    
    @Override
    public String optimizeMediaForWeb(String mediaUrl, MomentMedia.MediaType mediaType) {
        log.debug("Optimizing {} for web: {}", mediaType, mediaUrl);
        
        // This would typically use an image/video processing library
        // For this implementation, we'll return the original URL
        return mediaUrl;
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "mediaStats", key = "#userId")
    public MediaStatsDTO getMediaStatsByUser(Long userId) {
        log.debug("Getting media statistics for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<MomentMedia> allMedia = mediaRepository.findByUserOrderByCreatedAtDesc(user);
        
        if (allMedia.isEmpty()) {
            return MediaStatsDTO.builder()
                    .userId(userId)
                    .username(user.getUsername())
                    .totalMediaCount(0L)
                    .imageCount(0L)
                    .videoCount(0L)
                    .momentsWithMediaCount(0L)
                    .avgMediaPerMoment(0.0)
                    .totalStorageUsed(0L)
                    .mostUsedMediaType("None")
                    .build();
        }
        
        // Count media by type
        long imageCount = allMedia.stream()
                .filter(media -> media.getMediaType() == MomentMedia.MediaType.IMAGE)
                .count();
        
        long videoCount = allMedia.stream()
                .filter(media -> media.getMediaType() == MomentMedia.MediaType.VIDEO)
                .count();
        
        // Count unique moments with media
        Set<Long> momentsWithMedia = allMedia.stream()
                .map(media -> media.getMoment().getId())
                .collect(Collectors.toSet());
        
        long momentsWithMediaCount = momentsWithMedia.size();
        
        // Calculate average media per moment
        double avgMediaPerMoment = momentsWithMediaCount > 0 
                ? (double) allMedia.size() / momentsWithMediaCount
                : 0;
        
        // Get last upload date
        LocalDateTime lastUploadDate = allMedia.isEmpty() ? null : allMedia.get(0).getCreatedAt();
        
        // Determine most used media type
        String mostUsedMediaType = imageCount >= videoCount ? "Image" : "Video";
        
        // For a real implementation, we would calculate actual storage used
        // This is a placeholder calculation (10MB per image, 50MB per video)
        long totalStorageUsed = (imageCount * 10 + videoCount * 50) * 1024 * 1024;
        
        return MediaStatsDTO.builder()
                .userId(userId)
                .username(user.getUsername())
                .totalMediaCount((long) allMedia.size())
                .imageCount(imageCount)
                .videoCount(videoCount)
                .momentsWithMediaCount(momentsWithMediaCount)
                .avgMediaPerMoment(avgMediaPerMoment)
                .lastUploadDate(lastUploadDate)
                .totalStorageUsed(totalStorageUsed)
                .mostUsedMediaType(mostUsedMediaType)
                .build();
    }
    
    private String getFileExtension(String filename) {
        if (filename == null) {
            return "";
        }
        int dotIndex = filename.lastIndexOf('.');
        return (dotIndex == -1) ? "" : filename.substring(dotIndex + 1);
    }
    
    private MomentMedia.MediaType determineMediaType(String contentType) {
        if (contentType == null) {
            return MomentMedia.MediaType.IMAGE; // Default to image
        }
        
        if (contentType.startsWith("image/")) {
            return MomentMedia.MediaType.IMAGE;
        } else if (contentType.startsWith("video/")) {
            return MomentMedia.MediaType.VIDEO;
        } else {
            return MomentMedia.MediaType.IMAGE; // Default to image for unknown types
        }
    }
} 